#pragma once

#include "base.h"
#include "../updater/ohlc.h"
#include "../utils/time.h"

namespace mdtx::online::sampler
{
    template <typename Time, typename T, typename Duration = duration_type_t<Time>>
    class sliding : public sampler_base<Time, T, 3, 7>
    {
        using base = sampler_base<Time, T, 3, 7>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;

    protected:
        constexpr static size_t nslot = 2;
        size_t bsize;
        boost::circular_buffer<Time> buf_time;
        boost::circular_buffer<T> buf_vals;

        updater::ohlc_sliding<T> ohlc{};
        updater::vwap<T> vwap{};

        Duration window;

    public:
        explicit sliding(Duration window, size_t nbuf0 = 16)
            : bsize(nbuf0), buf_time(nbuf0), buf_vals(nbuf0 * nslot),
              window(window)
        {
        }

        /**
         * @brief update
         *
         * @param in time, price, vol, turnover
         * @return tuple_out time, open, high, low, close, vol, turnover, vwap
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [time, price, vol, tnvr] = in;

            ohlc.insert_impl(price);
            vwap.insert_impl(vol, tnvr);

            // resize buffer if full
            if (buf_time.full())
            {
                bsize *= 2;
                buf_time.set_capacity(bsize);
                buf_vals.set_capacity(bsize * nslot);
            }
            buf_time.push_back(time);
            bpush(buf_vals, vol, tnvr);

            ptrdiff_t pop{};
            Time barrier = time - window;
            for (auto t : buf_time)
            {
                if (t >= barrier)
                    break;
                ohlc.remove_impl(T{});
                vwap.remove_impl(buf_vals[0], buf_vals[1]);
                buf_vals.erase_begin(nslot);
                ++pop;
            }
            buf_time.erase_begin(pop);

            return tuple_out{detail::cat_time_tups(time, ohlc.get(), vwap.get())};
        }

        tuple_out flush() override
        {
            return tuple_out{};
        }
    };

    template <typename Time, typename T, typename Duration = duration_type_t<Time>>
    class sliding_gap : public sampler_base<Time, T, 3, 7>
    {
        using base = sampler_base<Time, T, 3, 7>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;

    protected:
        constexpr static size_t nslot = 2;
        size_t bsize;
        boost::circular_buffer<Time> buf_time;
        boost::circular_buffer<T> buf_vals;

        updater::ohlc_sliding<T> ohlc{};
        updater::vwap<T> vwap{};

        Time epoch, next{};
        Duration window, gap;
        bool init{};

    public:
        sliding_gap(Duration window, Duration gap, Time epoch = Time{}, size_t nbuf0 = 16)
            : bsize(nbuf0), buf_time(nbuf0), buf_vals(nbuf0 * nslot),
              epoch(epoch),
              window(window), gap(gap)
        {
        }

        /**
         * @brief update
         *
         * @param in time, price, vol, turnover
         * @return tuple_out time, open, high, low, close, vol, turnover, vwap
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [time, price, vol, tnvr] = in;

            if (!init)
            {
                next = align_window_start(time, window, epoch) + window;
                init = true;
            }

            tuple_out sample{};
            if (time > next)
            {
                sample = detail::cat_time_tups(next, ohlc.get(), vwap.get());

                while (next < time)
                {
                    next += gap;
                }

                ptrdiff_t pop{};
                Time barrier = next - window;
                for (auto t : buf_time)
                {
                    if (t >= barrier)
                        break;
                    ohlc.remove_impl(T{});
                    vwap.remove_impl(buf_vals[0], buf_vals[1]);
                    buf_vals.erase_begin(nslot);
                    ++pop;
                }
                buf_time.erase_begin(pop);
            }

            ohlc.insert_impl(price);
            vwap.insert_impl(vol, tnvr);

            if (buf_time.full())
            {
                bsize *= 2;
                buf_time.set_capacity(bsize);
                buf_vals.set_capacity(bsize * nslot);
            }
            buf_time.push_back(time);
            bpush(buf_vals, vol, tnvr);

            return sample;
        };

        tuple_out flush() override
        {
            tuple_out sample{detail::cat_time_tups(buf_time.back(), ohlc.pop(), vwap.pop())};
            return sample;
        }
    };

    template <typename Time, typename T>
    class sliding_volclk : public sampler_base<Time, T, 3, 7>
    {
        using base = sampler_base<Time, T, 3, 7>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;

    protected:
        constexpr static size_t nslot = 2;
        size_t bsize;
        boost::circular_buffer<T> buf_vals;

        T window;
        updater::ohlc_sliding<T> ohlc{};
        updater::vwap<T> vwap{};

    public:
        explicit sliding_volclk(T volume, size_t nbuf0 = 16)
            : bsize(nbuf0), buf_vals(nbuf0 * nslot),
              window(volume)
        {
        }

        /**
         * @brief update
         *
         * @param in time, price, vol, turnover
         * @return tuple_out time, open, high, low, close, vol, turnover, vwap
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [time, price, vol, tnvr] = in;

            ohlc.insert_impl(price);
            vwap.insert_impl(vol, tnvr);

            if (buf_vals.full())
            {
                bsize *= 2;
                buf_vals.set_capacity(bsize * nslot);
            }
            bpush(buf_vals, vol, tnvr);

            while (!buf_vals.empty() && vwap.vol > window)
            {
                ohlc.remove_impl(T{});
                vwap.remove_impl(buf_vals[0], buf_vals[1]);
                buf_vals.erase_begin(nslot);
            }

            return tuple_out{detail::cat_time_tups(time, ohlc.get(), vwap.get())};
        }

        tuple_out flush() override
        {
            return tuple_out{};
        }
    };

    template <typename Time, typename T>
    class sliding_tnvrclk : public sampler_base<Time, T, 3, 7>
    {
        using base = sampler_base<Time, T, 3, 7>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;

    protected:
        constexpr static size_t nslot = 2;
        size_t bsize;
        boost::circular_buffer<T> buf_vals;

        T window;
        updater::ohlc_sliding<T> ohlc{};
        updater::vwap<T> vwap{};

    public:
        explicit sliding_tnvrclk(T turnover, size_t nbuf0 = 16)
            : bsize(nbuf0), buf_vals(nbuf0 * nslot),
              window(turnover)
        {
        }

        /**
         * @brief update
         *
         * @param in time, price, vol, turnover
         * @return tuple_out time, open, high, low, close, vol, turnover, vwap
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [time, price, vol, tnvr] = in;

            ohlc.insert_impl(price);
            vwap.insert_impl(vol, tnvr);

            if (buf_vals.full())
            {
                bsize *= 2;
                buf_vals.set_capacity(bsize * nslot);
            }
            bpush(buf_vals, vol, tnvr);

            while (!buf_vals.empty() && vwap.tnvr > window)
            {
                ohlc.remove_impl(T{});
                vwap.remove_impl(buf_vals[0], buf_vals[1]);
                buf_vals.erase_begin(nslot);
            }

            return tuple_out{detail::cat_time_tups(time, ohlc.get(), vwap.get())};
        }

        tuple_out flush() override
        {
            return tuple_out{};
        }
    };
}
